#pragma once

#ifndef __CANVAS_STRING_H
#define __CANVAS_STRING_H

#include <memory.h>
#include "Objects.h"

namespace Canvas
{
	template<class T>
	class ObjectString : public Object
	{
	private:
		static const T zero;

		mutable T* buffer;
		mutable volatile int* counter;
		mutable int start;
		mutable int length;
		mutable int realLength;

		static int CalculateLength(const T* buffer)
		{
			int result = 0;
			while (*buffer++) result++;
			return result;
		}
	public:
		static int Compare(const T* bufA, const ObjectString<T>& strB)
		{
			const auto bufB = strB.buffer + strB.start;
			const auto bufAOld = bufA;
			auto length = strB.length;

			while (length-- && *bufA)
			{
				auto diff = *buffA++ - *bufB++;
				if (diff != 0)
					return diff;
			}
			return CalculateLength(bufAOld) - strB.length;
		}

		static int Compare(const ObjectString<T>& strA, const ObjectString<T>& strB)
		{
			const T* bufA = strA.buffer + strA.start;
			const T* bufB = strB.buffer + strB.start;

			auto length = strA.length < strB.length ? strA.length : strB.length;
			while (length--)
			{
				auto diff = *bufA++ - *bufB++;
				if (diff != 0)
					return diff;
			}
			return strA.length - strB.length;
		}

	private:
		void Inc() const
		{
			if (counter)
			{
				INCRC(counter);
			}
		}

		void Dec() const
		{
			if (counter)
			{
				if (DECRC(counter) == 0)
				{
					delete[] buffer;
					delete counter;
				}
			}
		}

		ObjectString(const ObjectString<T>& string, int _start, int _length)
		{
			if (_length <= 0)
			{
				buffer = (T*)&zero;
				counter = nullptr;
				start = 0;
				length = 0;
				realLength = 0;
			}
			else
			{
				buffer = string.buffer;
				counter = string.counter;
				start = string.start + _start;
				length = _length;
				realLength = string.realLength;
				Inc();
			}
		}

		ObjectString(const ObjectString<T>& dest, const ObjectString<T> source, int index, int count)
		{
			if (index == 0 && count == dest.length && source.length == 0)
			{
				buffer = (T*)&zero;
				counter = nullptr;
				start = 0;
				length = 0;
				realLength = 0;
			}
			else
			{
				counter = new int(1);
				start = 0;
				length = dest.length - count + source.length;
				buffer = new T[length + 1];
				memcpy(buffer, dest.buffer + dest.start, sizeof(T)*index);
				memcpy(buffer + index, source.buffer + source.start, sizeof(T)*source.length);
				memcpy(buffer + index + source.length, (dest.buffer + dest.start + index + count), sizeof(T)*(dest.length - index - count));
				buffer[length] = 0;
			}

		}

	public:
		static ObjectString<T> Empty;
		ObjectString(): buffer((T*)&zero), counter(nullptr), start(0), length(0), realLength(0) {}

		ObjectString(const T& _char)
		{
			counter = new int(1);
			start = 0;
			length = 1;
			buffer = new T[2];
			buffer[0] = _char;
			buffer[1] = zero;
			realLength = length;
		}

		ObjectString(const T* _buffer, int length)
		{
			if (_length <= 0)
			{
				buffer = (T*)&zero;
				counter = nullptr;
				start = 0;
				lenght = 0;
				realLength = 0;
			}
			else
			{
				buffer = new T[_length + 1];
				memcpy(buffer, _buffer, _length*sizeof(T));
				buffer[_length] = 0;
				counter = new int(1);
				start = 0;
				length = _length;
				realLength = _length;
			}
		}

		ObjectString(const T* _buffer, bool copy = true)
		{
			if (!_buffer || _buffer == nullptr || _buffer == 0)
			{
				throw NullPtrError();
			}
			if (copy)
			{
				counter = new int(1);
				start = 0;
				length = CalculateLength(_buffer);
				buffer = new T[length + 1];
				memcpy(buffer, _buffer, sizeof(T)*(length + 1));
				realLength = length;
			}
			else
			{
				buffer = (T*)_buffer;
				counter = nullptr;
				start = 0;
				length = CalculateLength(_buffer);
				realLength = length;
			}
		}

		ObjectString(const ObjectString<T>& string)
		{
			buffer = string.buffer;
			counter = string.counter;
			start = string.start;
			length = string.length;
			realLength = string.realLength;
			Inc();
		}

		ObjectString(ObjectString<T>&& string)
		{
			buffer = string.buffer;
			counter = string.counter;
			start = string.start;
			length = string.length;
			realLength = string.realLength;

			string.buffer = (T*)&zero;
			string.counter = nullptr;
			string.start = 0;
			string.length = 0;
			string.realLength = 0;
		}

		~ObjectString()
		{
			Dec();
		}

		const T* Buffer() const
		{
			if (start + length != realLength)
			{
				T* newBuffer = new T[length + 1];
				memcpy(newBuffer, buffer + start, sizeof(T)*length);
				newBuffer[length] = 0;
				Dec();
				buffer = newBuffer;
				counter = new int(1);
				start = 0;
				realLength = length;
			}
			return buffer + start;
		}

		ObjectString<T>& operator=(const ObjectString<T>& string)
		{
			if (this != &string)
			{
				Dec();
				buffer = string.buffer;
				counter = string.counter;
				start = string.start;
				length = string.length;
				realLength = string.realLength;
				Inc();
			}
			return *this;
		}

		ObjectString<T>& operator=(ObjectString<T>&& string)
		{
			if (this != &string)
			{
				Dec();
				buffer = string.buffer;
				counter = string.counter;
				start = string.start;
				length = string.length;
				realLength = string.realLength;

				string.buffer = (T*)&zero;
				string.counter = nullptr;
				string.start = 0;
				string.length = 0;
				string.realLength = 0;
			}
			return *this;
		}

		ObjectString<T>& operator+=(const ObjectString<T>& string)
		{
			return *this = *this + string;
		}

		ObjectString<T> operator+(const ObjectString<T>& string)const
		{
			return ObjectString<T>(*this, string, length, 0);
		}

		bool operator==(const ObjectString<T>& string) const { return Compare(*this, string) == 0; }
		bool operator!=(const ObjectString<T>& string) const { return Compare(*this, string) != 0; }
		bool operator> (const ObjectString<T>& string) const { return Compare(*this, string) >  0; }
		bool operator>=(const ObjectString<T>& string) const { return Compare(*this, string) >= 0; }
		bool operator< (const ObjectString<T>& string) const { return Compare(*this, string) <  0; }
		bool operator<=(const ObjectString<T>& string) const { return Compare(*this, string) <= 0; }

		bool operator==(const T* _buffer) const { return Compare(_buffer, *this) == 0; }
		bool operator!=(const T* _buffer) const { return Compare(_buffer, *this) != 0; }
		bool operator> (const T* _buffer) const { return Compare(_buffer, *this) <  0; }
		bool operator>=(const T* _buffer) const { return Compare(_buffer, *this) <= 0; }
		bool operator< (const T* _buffer) const { return Compare(_buffer, *this) >  0; }
		bool operator<=(const T* _buffer) const { return Compare(_buffer, *this) >= 0; }

		T operator[](int index) const
		{
			if (index >= length || index < 0)
			{
				throw OutOfRange();
			}
			return buffer[start + index];
		}

		int Length() const
		{
			return length;
		}

		int IndexOf(T c) const
		{
			const T* reading = buffer + start;
			for (int i = 0; i < length; i++)
			{
				if (reading[i] == c)
					return i;
			}
			return -1;
		}

		ObjectString<T> Left(int count) const
		{
			if (count < 0 || length < count)
			{
				throw OutOfRange();
			}
			return ObjectString<T>(*this, 0, count);
		}

		ObjectString<T> Right(int count) const
		{
			if (count < 0 || length < count) {
				throw OutOfRange();
			}
			return ObjectString(*this, length - count, count);
		}

		ObjectString<T> Sub(int index, int count) const
		{
			if (index < 0 || length < index || count < 0 || index + count > length)
			{
				throw OutOfRange();
			}
			return ObjectString<T>(*this, index, count);
		}

		ObjectString<T> Remove(int index, int count) const
		{
			if (index < 0 || length < index || count < 0 || index + count > length)
			{
				throw OutOfRange();
			}
			return ObjectString<T>(*this, ObjectString<T>(), index, count);
		}

		ObjectString<T> Insert(int index, const ObjectString<T>& string) const
		{
			if (index < 0 || index > length)
			{
				throw OutOfRange();
			}
			return ObjectString(*this, string, index, 0);
		}

		friend bool operator< (const T* left, const ObjectString<T>& right) { return Compare(left, right) <  0; }
		friend bool operator<=(const T* left, const ObjectString<T>& right) { return Compare(left, right) <= 0; }
		friend bool operator> (const T* left, const ObjectString<T>& right) { return Compare(left, right) >  0; }
		friend bool operator>=(const T* left, const ObjectString<T>& right) { return Compare(left, right) >= 0; }
		friend bool operator==(const T* left, const ObjectString<T>& right) { return Compare(left, right) == 0; }
		friend bool operator!=(const T* left, const ObjectString<T>& right) { return Compare(left, right) != 0; }

		friend ObjectString<T> operator+(const T* left, const ObjectString<T>& right) { return ObjectString<T>(left, false) + right; }
	};

	template<typename T>
	ObjectString<T> ObjectString<T>::Empty = ObjectString<T>();
	template<typename T>
	const T ObjectString<T>::zero = 0;

	typedef ObjectString<char> AString;
	typedef ObjectString<wchar_t> WString;
}

#endif